/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/// \file gameengine.h

#ifndef GAMELOGIC_H
#define GAMELOGIC_H

#include "defines.h"     // include this before anything else.  

#include "gameobject.h"
#include "spacecraft.h"
#include "frameinfo.h"
#include "gameeffect.h"
#include "gamelevel.h"

#include <QList>
#include <QQueue>
#include <QGLWidget>    // just for GLfloat...

#include <math.h>
#include <stdlib.h>

class Asteroid;
class Collectible;

class Ufo;
class Missile;
class BlackHole;
class Comet;

class SoundEngine;

class TimerManager;
class HyperJumpNavigator;
class HyperJumpDestination;

class ParticleData;

/*! \struct Statistics
 *
 * A structure for storing statistics information about
 * game objects and particle effects.
 */
struct Statistics
{
    int msecs;
    int numObjects;
    int numParticles;

    void reset() {
        msecs = 0;
        numObjects = 0;
        numParticles = 0;
    }

    void copy(const Statistics * s) {
        msecs = s->msecs;
        numObjects = s->numObjects;
        numParticles = s->numParticles;
    }
};

/*! \enum GameObjectStartMode
 * Start mode direction for a game object
 */
enum GameObjectStartMode
{
    /// Horizontal direction
    HorizontalDirection,
    /// Vertical direction
    VerticalDirection,
    /// Any direction
    AnyDirection
};

/*! \struct GameObjectStartParameters
 *
 * Structure for storing start parameters for game objects.
 */
struct GameObjectStartParameters
{
    /// Start X-coordinate
    FPtype startX;
    /// Start Y-coordinate
    FPtype startY;

    /// Start rotation angle
    FPtype angle;
    /// X-component for start velocity vector
    FPtype unitVelX;
    /// Y-component for start velocity vector
    FPtype unitVelY;

    /// End X-coordinate for MeteorShower
    FPtype endX;    // extra information for MeteorShowers...
    /// End Y-coordinate for MeteorShower
    FPtype endY;    // extra information for MeteorShowers...
};

enum { 
    /// Amount of miners at the game start. 
    MinersAtStartup = 5 
};

enum { 
    /// Amount of boms at the game start. 
    BombsAtStartup = 0 
};

enum { 
    /// Amount of hyper jumps at the game start. 
    HyperjumpsAtStartup = 0 
};

/*! \enum ProgressValues
 *
 * The progress value gained by destroying a game object 
 * or by collecting an item. Level progress is used to check the objectives
 * of a level and to bring more game objects to screen.
 */
enum ProgressValues
{
    /// Progress value for destroying a large asteroid with a bomb.
    /// 1 large asteroid + 3 * (1 medium sized asteroid + 4 minerals) = 16 progress points.
    ProgressLargeAsteroid = 16,
    /// Progress value for destroying a medium asteroid with a bomb.
    /// 1 medium sized asteroid + 4 minerals = 5 progress points.
    ProgressMediumAsteroid = 5,
    /// Progress value used to increment the progress when 
    /// - collectible 
    ///     - is collected
    ///     - expires, 
    ///     - is destoyed with a bomb
    /// - asteroid is split
    ProgressOther = 1
};

/*! \enum   ::PotentialObjects
 *
 * \brief   Amount of potential objects appended to game level as a result of adding a game object
 *          of certain type. 
 *
 * E.g. large asteroids splits into three medium
 * sized asteroid and each medium sized asteroid results four minerals:
 * \f$1 * 3 * 4 = 12\f$
 *
 * \sa GameEngine
 */
enum PotentialObjects
{
    /// Potential amount of resulting objects after adding one large asteroid to game 
    PotentialObjectsLargeAsteroid = 12,
    /// Potential amount of resulting objects after adding one medium sized asteroid to game 
    PotentialObjectsMediumAsteroid = 4,
    /// Potential amount of resulting objects after adding other game object than asteroid to game 
    PotentialObjectsOther = 1
};

class GameEngine : public QObject
{
    Q_OBJECT

public:

    /// Black hole effect distance, append black hole gravity effect.
    static const FPtype BlackHoleEffectDistance;
    /// Black hole force factor used when calculating black hole gravity effect impact on game object.
    static const FPtype BlackHoleForce;

    /// Large asteroid radius
    static const FPtype GameObjectRadiusAsteroidL;
    /// Medium sized asteroid radius
    static const FPtype GameObjectRadiusAsteroidM;
    /// Space ship AKA space craft AKA miner radius
    static const FPtype GameObjectRadiusSpaceCraft;
    /// Mineral collectible radius
    static const FPtype GameObjectRadiusMineral;
    /// UFO radius
    static const FPtype GameObjectRadiusUFO;
    /// Bomb collectible radius
    static const FPtype GameObjectRadiusBomb;
    /// Hyper jump collectible radius
    static const FPtype GameObjectRadiusHyperJump;
    /// Missile radius
    static const FPtype GameObjectRadiusMissile;
    /// Comet radius
    static const FPtype GameObjectRadiusComet;

    GameEngine(bool, int);
    ~GameEngine();

friend class GameWidget;
   
    // some static utility methods which may be used everywhere:

    /*! Returns a pseudo-random integer from range 0 to RAND_MAX.
     */
    static inline int getRandomI() { return rand(); }

    /*!
     * Returns a pseudo-random value as FPtype from range \f$ 0.0 \dots 1.0 \f$
     */
    static inline FPtype getRandomF() { return ((FPtype) rand() / (FPtype) RAND_MAX); }

    /*!
     * Normalized a give 3-component vector.
     * \param vect An array presenting a 3-component vector.
     */
    static inline void normalizeVect3D_GLfloat(GLfloat *vect) {
        const GLfloat sum = vect[0] * vect[0] + vect[1] * vect[1] + vect[2] * vect[2];
        const GLfloat scaling = 1.0f / sqrt(sum);
        vect[0] *= scaling;
        vect[1] *= scaling;
        vect[2] *= scaling;
    }

signals:

    /// Signal emitted when background image change is needed.
    void changeBackGroundImage(int level);
    /// Signal emitted when ''Game Over'' game state is needed (no more miners left). 
    void gameOver(int score, FPtype time);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    /// Signal emitted when player crashed to start the rewind mode playback.
    void playerCrashed();
#endif

private: // methods

    /// Return the X-steering value of miner
    inline FPtype steeringX() const { return minerSteeringX;};
    /// Return the Y-steering value of miner
    inline FPtype steeringY() const { return minerSteeringY;};


    /*! Sets the sound engine
     * \param engine pointer to a SoundEngine instance
     */
    void setSoundEngine(SoundEngine *engine) { soundEngine = engine; } // ownership not moved

    void setSteeringX(FPtype steeringX);
    void setSteeringY(FPtype steeringY);
    void setShakeZ(FPtype shakeZ);

    void setScreenSize(int width, int height);

    /// Returns the game screen width
    inline int screenWidth() const { return gameScreenWidth; };
    /// Returns the game screen height
    inline int screenHeight() const { return gameScreenHeight; };

    void initNewGame(bool rewindEnabled, int difficultyLevel);

    void cycleGameLoop(FPtype timestep);

    void initializeFrame(FPtype timestep);      // count frames and total time + rewind-related.
    void handleGameTimers(FPtype timestep);     // advance GameTimers and process events related in them.
    void controlSpaceShip(FPtype timestep);     // move the SpaceShip according to the user controls.
    void advanceGameObjects(FPtype timestep);   // move other game objects (asteroids, enemies).
    void detectCollisions();                    // detect game object collisions and manage related events.
    void controlGameEvents(FPtype timestep);    // manage random (other than collision based) game events.
    void removeUnnecessaryObjects();            // remove unnecessary GameObjects (as rewind allows).
    void processNewEffects();                   // activate GameEffects added at this frame + storage.

    bool isUnnecessary(GameObject *gameObject);
    bool isOffScreen(GameObject *gameObject, FPtype *margins);

    void resetSpaceShip(FPtype positionX, FPtype positionY);
 
    GLfloat shipModeStatus() const;
    void changeShipModeDelayed();
    void setShipModeImmediate(SpaceCraftMode mode);
    void transformShip();

    void shipHyperJumpOut();                        // take the ship out from screen.
    void shipHyperJumpIn(bool needSpaceShipReset);  // bring the ship into screen.

    // launch the user activated BombEffect at the given location.    
    void launchBomb(FPtype positionX, FPtype positionY);
    // launch the self activated BombEffect at the given location.
    void launchBombSelf(FPtype positionX, FPtype positionY);

    void integrateMotion(GameObject *gameObject, FPtype timestep);

    bool detect(const GameObject *gameObjectA, const GameObject *gameObjectB) const;

    bool isPlayerProtected();

    void handleMinerAsteroidCollision(int asteroidIndex);
    void handleAsteroidSplitting(int asteroidIndex);

    void handleMinerCollectibleCollision(int collectibleIndex);

    void handlePlayerCrash(const SoundEffectType type);
    void handlePlayerDies();

    void handleBumpCollision(GameObject *gameObjectA, GameObject *gameObjectB) const;

    Asteroid * createAsteroid(GameObjectType type, const Asteroid *asteroid = 0);
    Collectible * createCollectible(GameObjectType type, const GameObject *gameObject = 0);
    void createBomb(const Asteroid *asteroid);

    void addSpaceShipSteeringEffects(int timeStepAsInt);
    void addMissileParticleEffects(int timeStepAsInt);
    void addCometParticleEffects(int timeStepAsInt);

    void createParticleExplosion(FPtype x, FPtype y, int particleCount, FPtype particleVelocity = 0.05f);

    void addBlackHoleParticles(int timeStepAsInt);
    void addHyperJumpParticles(int timeStepAsInt);

    void doBlackHoleEffect(GameObject *gameObject);
    void setObjectDead(GameObject *gameObject, int frame, FPtype time);

    void initNextLevel();
    void moreGameObjects();

    void applyBombDestruction();

    void fireMissile(const Ufo *ufo);
    void steerMissile(Missile *missile, bool reset = false);

    /// Returns a pointer to \c ParticleData instance
    inline ParticleData &particleData() { return *particle; }

    void doResizeRecovery(int oldWidth, int oldHeight);

    void newSoundEffect(SoundEffectType type);

    void cleanUpGameObjects(bool complete);

    void getStartParams(GameObjectStartMode mode, FPtype marginRelative, 
                        GameObjectStartParameters &startParameters);

    bool objectivesFilled(int &currentObject, int &potentialObjects) const;

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    void checkPosition(GameObject *gameObject, FPtype marginX, FPtype marginY);
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    void setRewindEnabled(bool rewindEnabled);

    void cleanUpRewindFrames(bool cleanUpAlsoGameObjects);

    void writeObjectPositions();    // save the position of each GameObject.

    void initObjectsForRewindMode(int firstFrame);
    void resetTimersForRewindMode();

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

private: // data

    /// Sound engine for playing sound effects
    SoundEngine *soundEngine;
    /// Timer manager for managing game timers
    TimerManager *timerManager;

    /// Is rewind mode playback enabled 
    bool rewindEnabled;

    /// Game screen width
    int gameScreenWidth;
    /// Game screen height
    int gameScreenHeight;

    /// Game viewport X-range
    FPtype gameViewRangeX;
    /// Game viewport Y-range
    FPtype gameViewRangeY;
    /// Game viewport Z-range
    FPtype gameViewRangeZ;

    /// Contains current frame from the game start (initialized to 0 on each game start)
    unsigned int totalFrameCounter;
    /// Contains the current time from game start (initialized to 0 on each game start)
    FPtype totalTimeCounter;
    /// Rotation sensor readings adjusted close to range -1 <-> +1
    FPtype minerSteeringX;      
    /// Rotation sensor readings adjusted close to range -1 <-> +1
    FPtype minerSteeringY;      
    /// Acceleration sensor readings adjusted close to range 0 <-> 100
    FPtype hyperjumpShakeZ;     

    /// Miner game object controlled by player
    SpaceCraft *miner;
    /// Miner rocket level for X-axis particle effects
    int minerRocketLevelX;
    /// Miner rocket effects counter for X-axis particle effects 
    int minerRocketCounterX;
    /// Miner rocket level for Y-axis particle effects
    int minerRocketLevelY;
    /// Miner rocket effects counter for Y-axis particle effects 
    int minerRocketCounterY;

    /*! A list for storing new game effects on each game cycle, the effects are played at the end 
     * of the cycle
     */
    QList<GameEffect *> newEffects;

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE


    /*! For storing \c FrameInfo data for rewind mode playback.
     *  The FrameInfo objects are stored into a queue data stucture,
     *  since "first in / first out" addition and removal of FrameInfos
     *  is essential.
     */
    QQueue<FrameInfo *> frameInfoQ;

    /// For storing played effects for rewind mode playback.
    QQueue<GameEffect *> pastEffects;

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    /// For storing game statistics information
    Statistics currentFrameStats;

    /*! For storing asteroid game objects: asteroids may be ''alive'' ingame asteroids or ''dead''
        asteroids for rendering rewind mode playback.
    */
    QList<Asteroid*> asteroidList;
    /*! For storing collectible game objects: collectibles may be ''alive'' ingame collectibles 
        or ''dead'' collectibles for rendering rewind mode playback.
    */
    QList<Collectible*> collectibleList;
    /*! For storing enemy game objects: enemies may be ''alive'' ingame enemies 
        or ''dead'' enemies for rendering rewind mode playback.
    */
    QList<GameObject*> enemyList;

    /// Blackhole game object
    BlackHole *blackhole;
    /// Comet game object
    Comet *comet;

    /// Hyper jump navigator for computing a ''safe'' hyper jump targert location for miner.
    HyperJumpNavigator *hyperJumpNavigator;

    /// For requesting immidiate update on miner space ship graphics.
    bool updateSpaceShipGraphics;

    /// Counter for miners left (How many ''Lives'' player has)
    int minersLeft;

    GameLevel *level;
    GameLevelData levelData;
    int currentLevelProgress[2];
    FPtype bombProbability;

    /*! A list for Ufo triggers contains the progress points where new UFOs can be appended to 
     * game screen 
     */
    QList<int> triggerUfo;
    /*! A list for MeteorShower triggers contains the progress points whewe new meteor showers can 
        be appended to game screen 
    */
    QList<int> triggerMeteorShower;
    /*! A list for BlackHole triggers contains the progress points where new black holes can be 
     * appended to game screen 
     */
    QList<int> triggerBlackHole;
    /*! A list for Comet triggers contains the progress points where new comets can be appended 
     * to game screen 
     */
    QList<int> triggerComet;

    int gameScore;
    QList<int> score();

    int bombCount;
    int hyperJumpCount;

    ParticleData *particle;

    GLfloat asteroidMediumATranslationX;
    GLfloat asteroidMediumATranslationY;
    GLfloat asteroidMediumATranslationZ;

    GLfloat asteroidMediumBTranslationX;
    GLfloat asteroidMediumBTranslationY;
    GLfloat asteroidMediumBTranslationZ;

    GLfloat asteroidMediumCTranslationX;
    GLfloat asteroidMediumCTranslationY;
    GLfloat asteroidMediumCTranslationZ;

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    QList<GameObjectStartParameters> storedStartParamsDEBUG;

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

#ifdef SMASHMINER_CONFIG_ENABLE_TESTINGMODE

    void testingModeInit();

    // testing flags
    bool skipMinerCollision;

#endif  // SMASHMINER_CONFIG_ENABLE_TESTINGMODE

public:

    // commandline flags:
    //^^^^^^^^^^^^^^^^^^^^

    static bool commandlineShowBlackHole;
    static bool commandlineShowComet;

    static bool commandlineUseLocalFiles;

    // commandline variables:
    //^^^^^^^^^^^^^^^^^^^^^^^^

    static int commandlineStartLevel;
    static int commandlineStartLives;
    static int commandlineStartBombs;
    static int commandlineStartHyperjumps;

};

#endif // GAMELOGIC_H
